package ljl.bilibili.video.service.video_production.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.io.IoUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import io.minio.errors.*;
import ljl.bilibili.client.file.CustomMultipartFile;
import ljl.bilibili.client.notice.SendNoticeClient;
import ljl.bilibili.client.pojo.UploadVideo;
import ljl.bilibili.entity.user_center.user_info.User;
import ljl.bilibili.entity.video.video_production.upload.Video;
import ljl.bilibili.entity.video.video_production.upload.VideoData;
import ljl.bilibili.mapper.user_center.user_info.UserMapper;
import ljl.bilibili.mapper.video.video_production.upload.VideoDataMapper;
import ljl.bilibili.mapper.video.video_production.upload.VideoMapper;
import ljl.bilibili.util.Result;
import ljl.bilibili.video.pojo.UploadPart;
import ljl.bilibili.video.service.video_production.UploadAndEditService;
import ljl.bilibili.video.vo.request.video_production.DeleteVideoRequest;
import ljl.bilibili.video.vo.request.video_production.EditVideoRequest;
import ljl.bilibili.video.vo.request.video_production.UploadPartRequest;
import ljl.bilibili.video.vo.request.video_production.UploadVideoRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import ws.schild.jave.EncoderException;
import ws.schild.jave.MultimediaObject;
import ws.schild.jave.ScreenExtractor;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;

import static ljl.bilibili.video.constant.Constant.*;

/**
 * 视频上传和编辑服务实现类
 */
@Slf4j
@Service
public class UploadAndEditServiceImpl implements UploadAndEditService {
    @Resource
    MinioServiceImpl minioService; // MinIO服务接口，用于文件上传和管理

    @Resource
    VideoMapper videoMapper; // 视频数据映射器，用于操作视频表

    @Resource
    UserMapper userMapper; // 用户数据映射器，用于操作用户表

    @Resource
    SendNoticeClient client; // 发送通知客户端，用于发送各种通知

    @Resource
    VideoDataMapper videoDataMapper; // 视频数据映射器，用于操作视频数据表

    @Resource
    RedisTemplate objectRedisTemplate; // Redis模板，用于缓存操作

    /**
     * 判断是否恶意文件、上传视频到minio、新增视频与视频数据记录、推送视频动态、发送数据同步消息
     */
    @Override
    @Transactional
    public Result<Boolean> uploadTotal(UploadVideoRequest uploadVideoRequest) {
        try {
            // 将请求转换为视频实体
            Video video = uploadVideoRequest.toEntity();
            LambdaQueryWrapper<Video> queryWrapper = new LambdaQueryWrapper<>();
            String coverFile = uploadVideoRequest.getVideoCover();
            String url = "http://localhost:9000/bilibili/" + uploadVideoRequest.getUrl();
            video.setUrl(url);
            if (coverFile != null && !coverFile.isEmpty()) { // 如果有封面图片
                String prefixPath = "http://localhost:9000/bilibili/";
                byte[] decodedBytes = Base64.decode(coverFile); // 解码Base64编码的封面图片
//                hasCover=true;
//                ByteArrayInputStream bis = new ByteArrayInputStream(decodedBytes);
                // 创建ImageInputStream
//                ImageInputStream iis = ImageIO.createImageInputStream(bis);
                // 读取图片文件格式
//                String imgContentType = getImageFormat(iis);
//                log.info(imgContentType);
                String imgContentType = "image/jpeg"; // 设置图片类型为JPEG
                String coverFileName = video.getName() + UUID.randomUUID().toString().substring(0, 8) + ".jpg";
                video.setCover(prefixPath + coverFileName);
                // 插入视频记录和视频数据记录
                videoMapper.insert(video);
                videoDataMapper.insert(new VideoData().setVideoId(video.getId()));
                // 创建并上传封面文件到MinIO
                CustomMultipartFile coverMultipartFile = new CustomMultipartFile(decodedBytes, coverFileName, imgContentType);
                queryWrapper.eq(Video::getCover, UUID.randomUUID().toString().substring(0, 8) + coverFileName);
                minioService.uploadImgFile(coverFileName, coverMultipartFile.getInputStream(), imgContentType);
                // 发送上传通知和动态通知
                client.sendUploadNotice(new UploadVideo().setVideoId(video.getId()).setVideoName(video.getName()).setUrl(url).setHasCover(true));
                User user = userMapper.selectById(uploadVideoRequest.getUserId());
                client.dynamicNotice(uploadVideoRequest.toCoverDynamic(user, video));
            } else {// 如果没有封面图片
                // 插入视频记录和视频数据记录
                videoMapper.insert(video);
                videoDataMapper.insert(new VideoData().setVideoId(video.getId()));
                // 发送上传通知和动态通知
                client.sendUploadNotice(new UploadVideo().setVideoId(video.getId()).setVideoName(video.getName()).setUrl(url).setHasCover(false));
                User user = userMapper.selectById(uploadVideoRequest.getUserId());
                client.dynamicNotice(uploadVideoRequest.toNoCoverDynamic(user, video));
            }
            // 异步发送数据库变更通知
            CompletableFuture<Void> sendDBChangeNotice = CompletableFuture.runAsync(() -> {
                ObjectMapper objectMapper = new ObjectMapper();
                JavaTimeModule module = new JavaTimeModule();
                // 设置LocalDateTime的序列化方式
                LocalDateTimeSerializer localDateTimeSerializer = new LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
                module.addSerializer(LocalDateTime.class, localDateTimeSerializer);
                objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
                objectMapper.registerModule(module);
                Map<String, Object> map = objectMapper.convertValue(video, Map.class);
                map.put(TABLE_NAME, VIDEO_TABLE_NAME);
                map.put(OPERATION_TYPE, OPERATION_TYPE_ADD);
                map.put(VIDEO_ID, map.get(TABLE_ID));
                map.remove(TABLE_ID);
                client.sendDBChangeNotice(map);
            });

//            CompletableFuture.allOf(uploadVideoFuture, sendNoticeFuture).join();
//            if (!uploadVideoSuccess.get()) {
//                log.error("lose");
//            } else {
//                // 所有任务成功
//                log.info("ok");
//            }
            return Result.success(true);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("上传失败");
//            return Result.error("");
        }
    }

    /**
     * 编辑视频并发送数据同步消息
     */
    @Override
    public Result<Boolean> edit(EditVideoRequest editVideoRequest) {
        try {
            Map<String, Object> map = editVideoRequest.toMap();
            MultipartFile videoFile = editVideoRequest.getFile();
            ObjectMapper objectMapper = new ObjectMapper();
            JavaTimeModule module = new JavaTimeModule();
            LocalDateTimeSerializer localDateTimeSerializer = new LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
            module.addSerializer(LocalDateTime.class, localDateTimeSerializer);
            objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
            objectMapper.registerModule(module);
            //设置表名和操作类型
            map.put(TABLE_NAME, VIDEO_TABLE_NAME);
            map.put(OPERATION_TYPE, OPERATION_TYPE_UPDATE);
            Video video = editVideoRequest.toEntity();
            //如果上传了视频文件，则处理视频文件
            if (videoFile != null) {
                String videoUrl = UUID.randomUUID().toString().substring(0, 10) + editVideoRequest.getName();
                video.setUrl(videoUrl);
                map.put(VIDEO_URL, videoUrl);
                //异步上传视频文件到MinIO
                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                    try {
                        minioService.uploadVideoFile(videoUrl, videoFile.getInputStream(), videoFile.getContentType());
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                });
                //异步发送上传通知和动态通知
                CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
                    // 无限循环，直到成功发送上传通知或达到最大尝试次数
                    while (true) {
                        // 获取Redis中的计数器键的值
                        String key = "encode-count";
                        Integer count = (Integer) objectRedisTemplate.opsForValue().get(key);
                        // 检查当前计数值是否小于3，以决定是否发送上传通知
                        if (count < 3) {
                            // 发送上传通知，包含视频信息
                            client.sendUploadNotice(new UploadVideo().setVideoId(video.getId()).setUrl(videoUrl).setVideoName(video.getName()));
                            // 更新计数值
                            count++;
                            objectRedisTemplate.opsForValue().set(key, count);
                            // 退出循环
                            break;
                        } else {
                            // 如果计数值达到或超过3，等待5秒后再次尝试
                            try {
                                Thread.sleep(5000);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }

                    }
                });
            }
            //如果上传了封面图片，则处理封面图片
            MultipartFile coverFile = editVideoRequest.getCover();
            if (coverFile != null) {
                CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
                    String coverUrl = UUID.randomUUID().toString().substring(0, 10) + coverFile.getOriginalFilename();
                    map.put(VIDEO_COVER, coverUrl);
                    try {
                        minioService.uploadImgFile(coverUrl, coverFile.getInputStream(), coverFile.getContentType());
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                    //设置视频实体的封面图片URL
                    video.setCover(coverUrl);
                });
            }
            //异步发送数据库变更通知
            CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> client.sendDBChangeNotice(map));
            videoMapper.updateById(video);
            return Result.success(true);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error("编辑失败");
        }
    }

    /**
     * 删除视频并发送数据同步消息
     */
    @Override
    @Transactional
    public Result<Boolean> delete(DeleteVideoRequest deleteVideoRequest) {
        LambdaQueryWrapper<Video> deleteVideoWrapper = new LambdaQueryWrapper<>();
        LambdaQueryWrapper<VideoData> deleteVideoDataWrapper = new LambdaQueryWrapper<>();
        deleteVideoWrapper.eq(Video::getUserId, deleteVideoRequest.getUserId());
        deleteVideoWrapper.eq(Video::getId, deleteVideoRequest.getVideoId());
        deleteVideoDataWrapper.eq(VideoData::getVideoId, deleteVideoRequest.getVideoId());
        //异步发送数据变更通知
        CompletableFuture<Void> sendDBChangeNotice = CompletableFuture.runAsync(() -> {
            ObjectMapper objectMapper = new ObjectMapper();
            JavaTimeModule module = new JavaTimeModule();
            LocalDateTimeSerializer localDateTimeSerializer = new LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
            module.addSerializer(LocalDateTime.class, localDateTimeSerializer);
            objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
            objectMapper.registerModule(module);
            //构建数据变更通知的消息内容
            Map<String, Object> map = new HashMap<>(3);
            map.put(TABLE_NAME, VIDEO_TABLE_NAME);
            map.put(OPERATION_TYPE, OPERATION_TYPE_DELETE);
            map.put(TABLE_ID, deleteVideoRequest.getVideoId());
            //发送数据变更通知
            client.sendDBChangeNotice(map);
        });
        // 执行视频和视频数据的删除操作
        videoMapper.delete(deleteVideoWrapper);
        videoDataMapper.delete(deleteVideoDataWrapper);
        return Result.success(true);
    }

    /**
     * 上传视频时获取视频封面
     */
    @Override
    public Result<List<String>> uploadPart(UploadPartRequest uploadPartRequest) throws IOException, EncoderException, ServerException, InsufficientDataException, ErrorResponseException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        // 提取视频唯一标识符中的视频ID部分
        int commaIndex = uploadPartRequest.getResumableIdentifier().indexOf(',');
        uploadPartRequest.setResumableIdentifier(uploadPartRequest.getResumableIdentifier().substring(0, commaIndex));
        String resumableIdentifier = uploadPartRequest.getResumableIdentifier();
        String videoName = "";
        String videoCover = "";
//        if(uploadPartRequest.getResumableChunkNumber()==1){
//            String path = Files.createTempDirectory(".tmp").toString();
//            File file = new File(path, "test");
//            InputStream videoFileInputStream = uploadPartRequest.getFile().getInputStream();
//            byte[] bytes=IoUtil.readBytes(videoFileInputStream);
//            ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(bytes);
//            Files.copy(byteArrayInputStream, Paths.get(file.getAbsolutePath()), StandardCopyOption.REPLACE_EXISTING);
//            if (!FileTypeUtil.getType(file).equals("mp4")) {
//                file.delete();
//                return Result.error("上传恶意文件");
//            } else {
//                file.delete();
//                log.info("文件无问题");
//            }
//        }
        // 检查是否需要提取视频封面
        if (uploadPartMap.get(resumableIdentifier) == null || uploadPartMap.get(resumableIdentifier).getHasCutImg() == false) {
            // 从请求中获取视频文件输入流
            InputStream videoFileInputStream = uploadPartRequest.getFile().getInputStream();
            byte[] bytes = IoUtil.readBytes(videoFileInputStream);
            // 创建临时目录和文件以处理视频
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
            String filePath = Files.createTempDirectory(".tmp").toString();
            String coverFileName = UUID.randomUUID().toString().substring(0, 10) + ".jpg";
            String videoFileName = "video";
            File directory = new File(filePath);
            File videoFile = new File(filePath, videoFileName);
            File coverFile = new File(directory, coverFileName);
            // 将视频数据写入临时文件
            Files.copy(byteArrayInputStream, Paths.get(videoFile.getAbsolutePath()), StandardCopyOption.REPLACE_EXISTING);
            // 使用ScreenExtractor提取视频封面(使用jave库)
            ScreenExtractor screenExtractor = new ScreenExtractor();
            // 创建MultimediaObject实例以处理视频
            MultimediaObject multimediaObject = new MultimediaObject(videoFile);
            // 表示对视频在第1000毫秒(1秒)处截取帧，不改变其宽高，并将其保存为图像文件存入到coverFile文件夹中
            screenExtractor.renderOneImage(multimediaObject, -1, -1, 1000, coverFile, 1);
            // 如果封面文件成功创建，则将其转换为Base64编码字符串并保存
            if (coverFile.exists()) {
                log.info("exist");
                InputStream inputStream = new FileInputStream(coverFile);
                String cover = Base64.encode(IoUtil.readBytes(inputStream));
                log.info(cover.substring(0, 20));
                // 删除临时文件
                videoFile.delete();
                coverFile.delete();
                // 更新上传部分信息
                UploadPart uploadPart = uploadPartMap.getOrDefault(resumableIdentifier, new UploadPart());
                uploadPart.setHasCutImg(true);
                uploadPart.setCover(cover);
                uploadPartMap.put(resumableIdentifier, uploadPart);
            }
        }
        // 生成唯一的视频名称并上传视频文件
        String name = resumableIdentifier + UUID.randomUUID().toString().substring(0, 10);
        minioService.uploadVideoFile(name, uploadPartRequest.getFile().getInputStream(), VIDEO_TYPE);
//        log.info(uploadPartMap.toString());
        // 更新上传部分的映射信息
        Map<Integer, String> newUploadPartMap = uploadPartMap.getOrDefault(resumableIdentifier, new UploadPart()).getPartMap();
        newUploadPartMap.put(uploadPartRequest.getResumableChunkNumber(), name);
        UploadPart uploadPart = uploadPartMap.getOrDefault(resumableIdentifier, new UploadPart());
        uploadPart.setPartMap(newUploadPartMap);
        uploadPartMap.put(resumableIdentifier, uploadPart);
        // 更新总块计数
        uploadPartMap.get(resumableIdentifier).setTotalCount(uploadPartMap.get(resumableIdentifier).getTotalCount() + 1);
        // 如果所有视频块都已上传，则合并视频块
        if (uploadPartMap.get(resumableIdentifier).getTotalCount().equals(uploadPartRequest.getResumableTotalChunks())) {
            log.info("合并");
            videoName = resumableIdentifier + UUID.randomUUID().toString().substring(0, 10);
            videoCover = uploadPartMap.get(resumableIdentifier).getCover();
            minioService.composePart(resumableIdentifier, videoName);
        }
        List<String> list = new ArrayList<>();
        list.add(videoName);
        list.add(videoCover);
        return Result.data(list);
    }

    /**
     * 根据标识符和块编号获取处理器状态
     * 该方法用于检查给定标识符和块编号的处理状态，主要用于断点续传功能
     */
    @Override
    public ResponseEntity<Result<Boolean>> getProcessor(String resumableIdentifier, Integer resumableChunkNumber) {
        log.info("id" + resumableChunkNumber.toString());
        log.info("uploadMap" + uploadPartMap.toString());
        log.info(resumableIdentifier);
        log.info(uploadPartMap.getOrDefault(resumableIdentifier, new UploadPart()).toString());
        for (Map.Entry<Integer, String> entry : uploadPartMap.getOrDefault(resumableIdentifier, new UploadPart()).getPartMap().entrySet()) {
            log.info("键值对" + entry.toString());
            log.info(entry.getKey().intValue() + "和" + resumableChunkNumber.intValue());
            if (entry.getKey().intValue() == (resumableChunkNumber.intValue())) {
                log.info("200");
                return ResponseEntity.ok(Result.data(true));
            }
        }
        return ResponseEntity.noContent().build();
    }

    /**
     * 获取图像格式
     * 该方法通过读取图像的输入流来确定图像的格式名称
     */
    private static String getImageFormat(ImageInputStream iis) throws IOException {
        Iterator<ImageReader> imageReaders = ImageIO.getImageReaders(iis);
        if (imageReaders.hasNext()) {
            ImageReader reader = imageReaders.next();
            return reader.getFormatName();
        }
        return null;
    }
}
